package common

import (
	"bytes"
	"encoding/json"
	"fmt"
	"github.com/mitchellh/mapstructure"
	"gopkg.in/yaml.v2"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"os"
	"path/filepath"
	"reflect"
	"strconv"
	"strings"
	"syscall"
	"time"
)

// 读取yaml文件至结构体中
func LoadYAML(filename string, config interface{}) error {
	var c map[string]interface{}
	yamlAbsPath, err := filepath.Abs(filename)
	if err != nil {
		return err
	}

	// read the raw contents of the file
	data, err := ioutil.ReadFile(yamlAbsPath)
	if err != nil {
		return err
	}
	// put the file's contents as yaml to the default configuration(c)
	if err := yaml.Unmarshal(data, &c); err != nil {
		return err
	}
	if err := mapstructure.Decode(c, &config); err != nil {
		return err
	}

	return nil
}

// 解析http返回结果至结构体
func LoadResponse(resp *http.Response, result interface{}) error {
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	if err = json.Unmarshal(body, result); err != nil {
		return err
	}
	return nil
}

// 用b的所有字段覆盖a的
// 如果fields不为空, 表示用b的特定字段覆盖a的
// a应该为结构体指针
func CopyFields(a interface{}, b interface{}, fields ...string) (err error) {
	at := reflect.TypeOf(a)
	av := reflect.ValueOf(a)
	bt := reflect.TypeOf(b)
	bv := reflect.ValueOf(b)

	// 参数a 不允许非指针传入
	if at.Kind() != reflect.Ptr {
		panic("第一个参数必须为指针类型")
	}

	// 参数b 必须为结构体实体 不允许为指针
	if bv.Kind() == reflect.Ptr {
		panic("第二个参数必须为结构体值类型")
	}
	av = reflect.ValueOf(av.Interface())

	// 要复制哪些字段
	_fields := make([]string, 0)
	if len(fields) > 0 {
		_fields = fields
	} else {
		for i := 0; i < bv.NumField(); i++ {
			_fields = append(_fields, bt.Field(i).Name)
		}
	}

	if len(_fields) == 0 {
		fmt.Println("no fields to copy")
		return
	}

	// 复制
	for i := 0; i < len(_fields); i++ {
		name := _fields[i]
		f := av.Elem().FieldByName(name)
		bValue := bv.FieldByName(name)

		// a中有同名的字段并且类型一致才复制
		if f.IsValid() && f.Kind() == bValue.Kind() {
			f.Set(bValue)
		} else {
			fmt.Printf("no such field or different kind, fieldName: %s\n", name)
		}
	}
	return
}

// 结构体转map
func StructToMap(obj interface{}) map[string]interface{} {
	t := reflect.TypeOf(obj)
	v := reflect.ValueOf(obj)

	var data = make(map[string]interface{})
	for i := 0; i < t.NumField(); i++ {
		data[t.Field(i).Name] = v.Field(i).Interface()
	}
	return data
}

// 获取图片 buf 的类型
func ImageFileExt(buf []byte) (ext string) {
	var headerByte = buf[0:7]
	xStr := fmt.Sprintf("%x", headerByte)
	switch {
	case bytes.Equal([]byte{0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a}, buf[0:8]) || xStr == "89504e470d0a1a0a":
		ext = ".png"
	case xStr == "0000010001002020":
		ext = ".ico"
	case xStr == "0000020001002020":
		ext = ".cur"
	case xStr[:12] == "474946383961" || xStr[:12] == "474946383761":
		ext = ".gif"
	case xStr[:10] == "0000020000" || xStr[:10] == "0000100000":
		ext = ".tga"
	case xStr[:8] == "464f524d":
		ext = ".iff"
	case xStr[:8] == "52494646":
		ext = ".ani"
	case xStr[:4] == "4d4d" || xStr[:4] == "4949":
		ext = ".tiff"
	case xStr[:4] == "424d":
		ext = ".bmp"
	case xStr[:4] == "ffd8" || bytes.Equal([]byte{0xff, 0xd8, 0xff}, buf[0:3]):
		ext = ".jpg"
	case xStr[:2] == "0a":
		ext = ".pcx"
	default:
		ext = ""
	}
	return ext
}

// 是否为网络错误
func isCaredNetError(err error) bool {
	netErr, ok := err.(net.Error)
	if !ok {
		return false
	}
	if netErr.Timeout() {
		log.Println("timeout")
		return true
	}
	opErr, ok := netErr.(*net.OpError)
	if !ok {
		return false
	}
	switch t := opErr.Err.(type) {
	case *net.DNSError:
		log.Printf("net.DNSError:%+v", t)
		return true
	case *os.SyscallError:
		log.Printf("os.SyscallError:%+v", t)
		if errno, ok := t.Err.(syscall.Errno); ok {
			switch errno {
			case syscall.ECONNREFUSED:
				log.Println("connect refused")
				return true
			case syscall.ETIMEDOUT:
				log.Println("timeout")
				return true
			}
		}
	}

	return false
}

// 将颜色字符串转换为rgb
func ColorToRgb(colorStr string) (r, g, b int) {
	colorStr = strings.TrimPrefix(colorStr, "#")
	color64, err := strconv.ParseInt(colorStr, 16, 32)
	if err != nil {
		return
	}
	colorInt := int(color64)
	return colorInt >> 16, (colorInt & 0x00FF00) >> 8, colorInt & 0x0000FF
}

// 日期转时间戳
func DateToTimestamp(str string) int64 {
	stamp, _ := time.ParseInLocation("2006-01-02", str, time.Local)
	return stamp.Unix()
}
